package day18.Junit_reflect_annotation_proxy.reflectdemo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


//尝试用Junit，测试方法里面的内容就是测试内容，也不一定是一定要调业务方法，毕竟调了也是为了测试这个方法里面的代码去测试。
// 那我不调，测的就是我写的代码
public class demo2 {
    @Test
    public void func1()
    {
        //获取类的信息
                           //不去写预言，那么只会测会不会抛异常
        Class c1= Dog.class;
        System.out.println(c1.getName());//类名的名字，包含包名字
        System.out.println(c1.getSimpleName());//只包含类的名字

//      在反射中，类中的构造器的类型是Constructor，成员变量或者静态变量的类型是Field,方法的类型是Method类型。
//        比如Constructor con1=c1.getConstructor();  就是con1指向了c1的无参（没写参数）构造器（Constructor），区分是否是同一个方法并不是靠名字而是形参的个数，顺序等等，反正不靠名字。
    }


    //获取类的构造器
    @Test
    public void getConstructorInfo() throws Exception {
       Class c1=Dog.class;            //******  一个是String类型的实例化对象 str等等，一个是类 类型的实例化对象：int，String，double,boolean
       Constructor con1=c1.getDeclaredConstructor();//这个方法的形参是class类型也就是类 类型(class类型的实例化对象就是我声明的类)的比如说：他需要一个指向String 类型的class对象那就提供String.class获取指向String类型的class对象就行。
                            //以前那种普通的，他的形参是String类型的变量，着重在String的实例化对象， 而这个方法，着重在拿到String这个类（获取指向String这个类的class对象）毕竟任何类也都是一个对象。都是某个特别大的类实例化过来的
      //  参数的数量决定了不同的方法而不是名字                    //不写参数就是获得无参的构造器，写几个获得几个参数的构造器
       Constructor con2=c1.getDeclaredConstructor(String.class,int.class);//其实String.class就是String这个类。拿到指向String这个类的class对象，其实用的时候都是把他当成，本类用。
                                         // String class其实就是Class<String>
        System.out.println(con1.getName() + " "+con1.getParameterCount());//打印构造器的名称，和参数数量
        System.out.println(con2.getName() + " "+con2.getParameterCount());//这只是访问（拿构造器的属性名字等等，并没有去调用）不是调用，不太影响人家的private。但能访问已经挺厉害了

        //获取全部的构造器
        Constructor[] cons=c1.getDeclaredConstructors();//这是constructors复数，返回一个Constructor类型数组，把构造器装到数组里面
        System.out.println("************");
    //获取到构造器，尝试去创建对象
        //暴力反射：临时绕开private权限，去调用构造器
        con1.setAccessible(true);
        Dog dog1=(Dog)con1.newInstance();//调用无参构造器，但由于构造器是private的，不允许调用,返回的是Object类型
        System.out.println(dog1);
       Dog dog2=(Dog)con2.newInstance("小黑", 2);//本身就是public所以不需要暴力反射
       System.out.println(dog2);

    }
    //获取类的成员变量对象并对其进行操作
    @Test
    public void getFieldInfo() throws Exception
    {
        Class c1=Dog.class;
        //获取成员变量对象
           Field[] fds=c1.getDeclaredFields();  //getdeclaredFields拿到已经声明过的成员变量包括privite
           System.out.println(fds.length);
           System.out.println(fds);
        for(Field f:fds)
           {
               System.out.println(f.getName());
           }
        System.out.println("************");
        Field fd=c1.getDeclaredField("name");//形参是String类型的（String类的实例化对象），根据成员变量的名字去定位
        System.out.println("获取到的特定的成员变量名字："+fd.getName()+"类型："+fd.getType());
        Field fd2=c1.getDeclaredField("name");//形参是String类型的（String类的实例化对象），根据成员变量的名字去定位
       // 这只是访问（类的成员变量或者静态变量的名字等等反正不是数值）不是调用，不太影响人家的private。但能访问（之前连访问他的名字都不让）已经挺厉害了,
        // 这种访问可以建立在没有实例化对象的前提下。但是要是修改或者获取变量的数值的话就必须要实例化对象为前提



        fd2.setAccessible(true);//这个绝对不是修改，而是临时绕过      这个怎么理解呢？就是类中所有变量都是Field类型，fd是Field实例化的对象。fd指向这个变量，当然fd2也可以指向这个变量。
                      // 我在用fd去修改数值或者访问数值的话，就必须把fd暴力一下，但是fd2没有暴力，所以他不能访问和修改
        fd2.set(new Dog("小为",3),"en");

        fd.setAccessible(true);//fd拿到的变量是private需要绕开一下，暴力反射,这样就可以通过fd去修改数值，但是你要是通过fd2拿到这个成员变量，不去把fd2暴力一下，还是不能修改
        Dog dog3=new Dog("小为",3);
        fd.set(dog3, "小紫");//为成员变量赋值，但是成员变量存在的前提要有实例化的对象，第一个参数就是哪个对象，第二个成员变量就是设置成什么值
        System.out.println(fd);//这个打印的是name这个成员变量。他的名字（我起的）
        System.out.println(dog3);
        System.out.println(fd.get(dog3));//这个样子就是name变量.get（对象）就体现了反射。就是一种反转
    }
    //获取成员方法
    @Test
    public void getMethodInfo() throws Exception
    {
        Class c1=Dog.class;
        //获取全部的成员方法
        Method[] methods=c1.getDeclaredMethods();
        System.out.println("一共"+methods.length+"个方法");
        for(Method m:methods)
        {
            System.out.println(m.getName());
        }
        System.out.println("************");
        Method m=c1.getDeclaredMethod("run",int.class);//拿的是有参的run  参数的数量决定了不同的方法而不是名字
        Method m2=c1.getDeclaredMethod("run");//拿的是无参的run
        System.out.println("获取到的特定方法名称："+m.getName()+"返回类型："+m.getReturnType()+"参数数量："+m.getParameterCount());

        //调用方法
        //m拿到的方法是成员方法，需要要实例化对象才能调用
        Dog dog=new Dog("小黑",2);
        m.setAccessible(true);
        String result=(String)m.invoke(dog,5);//调用方法 第一个参数是调用的哪个对象的，第二个是要传的参数  invoke 的返回值就是你调用的哪个方法的返回值，返回的类型是Object类型，转换一下，不能子类指向父类
        System.out.println(result);
    }

}
//写一个狗类，狗类有名字，年龄
@Data
class Dog {
    private String name;
    private int age;
    private Dog()
    {
        System.out.println("无参构造器的执行");

    }
    private Dog(String name)
    {
        System.out.println("1个参构造器的执行");
        this.name=name;
    }
    public Dog(String name,int age)
    {
        System.out.println("2个参数构造器的执行");
        this.name=name;
        this.age=age;
    }
    private String run(int a)
    {
        System.out.print(name);
        System.out.println("  跑跑跑1");
        return "谢谢";

    }
    public void run()
    {
        System.out.println("跑跑跑0");
    }
  private static void show()
    {
        System.out.println("看门看门");
    }
}

